package com.axzhengxin.platform.afterloan.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.axzhengxin.platform.afterloan.controller.InventoryLoanInfoController.InventoryLoanInfoImportResult;
import com.axzhengxin.platform.afterloan.domain.BatchImportLoanDetail;
import com.axzhengxin.platform.afterloan.domain.FspCorInf;
import com.axzhengxin.platform.afterloan.domain.InventoryLoanInfo;
import com.axzhengxin.platform.afterloan.mapper.BatchImportLoanDetailMapper;
import com.axzhengxin.platform.afterloan.mapper.FspCorInfMapper;
import com.axzhengxin.platform.afterloan.mapper.InventoryLoanInfoMapper;
import com.axzhengxin.platform.afterloan.service.IInventoryLoanInfoService;
import com.ruoyi.common.annotation.DataScope;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.exception.CustomException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * InventoryLoanInfoServiceImpl
 * 存量业务Service业务层处理实现
 *
 * @author jiangnan
 * @date 2021/4/1
 */
@Service
@Slf4j
public class InventoryLoanInfoServiceImpl implements IInventoryLoanInfoService {

    private static List<Field> excelValueFields = Stream.of(ReflectUtil.getFields(InventoryLoanInfo.class))
            .filter(Objects::nonNull)
            .filter(InventoryLoanInfoServiceImpl::hasExcelAnnotation)
            .collect(Collectors.toList());

    @Autowired
    private InventoryLoanInfoMapper inventoryLoanInfoMapper;

    @Autowired
    private FspCorInfMapper fspCorInfMapper;

    @Autowired
    private BatchImportLoanDetailMapper batchImportLoanDetailMapper;


    /**
     * 检查指定的field上是否有指定类型的注解
     *
     * @param field 字段对象
     * @return 判断结果
     */
    private static boolean hasExcelAnnotation(@NotNull Field field) {
        return ArrayUtil.isNotEmpty(field.getAnnotationsByType(Excel.class));
    }

    /**
     * 查询存量业务
     *
     * @param id 存量业务ID
     * @return 存量业务
     */
    @Override
    public InventoryLoanInfo selectInventoryLoanInfoById(Long id) {
        return inventoryLoanInfoMapper.selectInventoryLoanInfoById(id);
    }

    /**
     * 查询存量业务列表
     *
     * @param query 查询参数
     * @return 存量业务
     */
    @DataScope(userAlias = "u", deptAlias = "d")
    @Override
    public List<InventoryLoanInfo> selectInventoryLoanInfoList(InventoryLoanInfo query) {
        List<InventoryLoanInfo> list = inventoryLoanInfoMapper.selectInventoryLoanInfoListWithDataScope(query);
        for (InventoryLoanInfo loanInfo : list) {
            loanInfo.setIfAuth("N");
            if ("Y".equalsIgnoreCase(query.getIfAuth())) {
                loanInfo.setIfAuth("Y");
            } else if ("N".equalsIgnoreCase(query.getIfAuth())) {
                loanInfo.setIfAuth("N");
            } else if (StrUtil.isBlank(loanInfo.getCustomerId())) {
                loanInfo.setIfAuth("N");
            } else {
                Optional.ofNullable(fspCorInfMapper.selectFspCorInfById(loanInfo.getCustomerId()))
                        .map(FspCorInf::getAuthFilePath)
                        .filter(StrUtil::isNotBlank)
                        .ifPresent(path -> loanInfo.setIfAuth("Y"));
            }
        }
        return list;
    }

    /**
     * 新增存量业务
     *
     * @param inventoryLoanInfo 存量业务
     * @return 结果
     */
    @Override
    public int insertInventoryLoanInfo(InventoryLoanInfo inventoryLoanInfo) {
        inventoryLoanInfo.setCreateTime(DateUtils.getNowDate());
        int i = inventoryLoanInfoMapper.insertInventoryLoanInfo(inventoryLoanInfo);
        fillBatchImportLoanDetail(inventoryLoanInfo);
        return i;
    }

    /**
     * 修改存量业务
     *
     * @param inventoryLoanInfo 存量业务
     * @return 结果
     */
    @Override
    public int updateInventoryLoanInfo(InventoryLoanInfo inventoryLoanInfo) {
        inventoryLoanInfo.setUpdateTime(DateUtils.getNowDate());

        BatchImportLoanDetail batchImportLoanDetail = new BatchImportLoanDetail(inventoryLoanInfo.getCustomerName(), inventoryLoanInfo.getUniscid(),
                inventoryLoanInfo.getAreaCode(), inventoryLoanInfo.getLoanContractNo(), inventoryLoanInfo.getId().toString());
        batchImportLoanDetail.setCreateBy(inventoryLoanInfo.getCreateBy());
        batchImportLoanDetailMapper.updateBatchImportLoanDetail(batchImportLoanDetail);
        return inventoryLoanInfoMapper.updateInventoryLoanInfo(inventoryLoanInfo);
    }

    /**
     * 批量删除存量业务
     *
     * @param ids 需要删除的存量业务ID
     * @return 结果
     */
    @Override
    public int deleteInventoryLoanInfoByIds(Long[] ids) {
        try {
            batchImportLoanDetailMapper.deleteByLoanInfoIds(ids);
            log.info("同步删除成功");
        } catch (Exception e) {
            log.info("同步删除失败");
            e.printStackTrace();
        }
        return inventoryLoanInfoMapper.deleteInventoryLoanInfoByIds(ids);
    }

    /**
     * 删除存量业务信息
     *
     * @param id 存量业务ID
     * @return 结果
     */
    @Override
    public int deleteInventoryLoanInfoById(Long id) {
        return inventoryLoanInfoMapper.deleteInventoryLoanInfoById(id);
    }

    /**
     * 导入存量业务信息
     *
     * @param loanInfoList  信息集合
     * @param updateSupport 是否支持更新
     * @return 消息
     */
    @Override
    public InventoryLoanInfoImportResult importInventoryLoanInfo(List<InventoryLoanInfo> loanInfoList,
                                                                 boolean updateSupport) {
        if (CollUtil.isEmpty(loanInfoList)) {
            throw new CustomException("导入存量业务数据不能为空");
        }
        AtomicInteger successCount = new AtomicInteger(0);
        InventoryLoanInfoImportResult result = new InventoryLoanInfoImportResult();
        // 导入数据前检查
        importPreValidation(loanInfoList, result.getFailureMessageList());
        // 判断是否通过
        if (!result.failure()) {
            // 根据数据是否已存在来判断是插入还是新增，填写相应的数据
            fillWithOtherInfo(loanInfoList)
                    .forEach(loanInfo -> {
                        // 根据主键字段判断是插入还是更新
                        inventoryLoanInfoMapper.insertInventoryLoanInfo(loanInfo);
                        //同步数据
                        fillBatchImportLoanDetail(loanInfo);
                        // 操作成功计数
                        successCount.incrementAndGet();
                    });
            result.setSuccessCount(successCount.get());
        }
        return result;
    }


    /**
     * 数据导入前检查
     * 如果执行完本方法后，作为参数传进来的failureMessage不为empty，则代表导入前检查未能通过
     * 此时应当立即终止操作，返回错误消息
     *
     * @param loanInfoList   导入存量数据对象集合
     * @param failureMessage 错误消息集合
     */
    private void importPreValidation(List<InventoryLoanInfo> loanInfoList, List<String> failureMessage) {
        // 前置检查1：任何本批次内贷款合同号重复的数据都会导致无法通过检查
        loanInfoList.stream()
                .collect(Collectors.groupingBy(InventoryLoanInfo::getLoanContractNo))
                .entrySet()
                .stream()
                .filter(entry -> CollUtil.isNotEmpty(entry.getValue()))
                .filter(entry -> entry.getValue().size() > 1)
                .map(Map.Entry::getKey)
                .map(name -> StrUtil.format("贷款合同号:{}的存量数据导入前检查失败，本批次下有多笔记录", name))
                .forEach(failureMessage::add);
        // 前置检查2：任何空值字段都会导致无法通过检查
        loanInfoList
                .stream()
                .flatMap(loanInfo -> excelValueFields.stream()
                        .map(field -> getFailureMessageIfNullFieldValue(loanInfo, field)))
                .filter(StrUtil::isNotBlank)
                .forEach(failureMessage::add);
        // 前置检查3：对金额字段进行检查。不得大于50000(万元)，即5亿元
        BigDecimal maxAmount = BigDecimal.valueOf(50000.00);
        loanInfoList
                .forEach(loanInfo -> {
                    if (NumberUtil.isGreater(loanInfo.getApproveAmount(), maxAmount)) {
                        failureMessage.add(StrUtil.format(
                                "贷款合同号:{}的存量数据导入前检查失败，原因为字段{}值超过最大限制:{}(万元)，请检查输入值",
                                loanInfo.getLoanContractNo(), "授信金额", maxAmount));
                    }
                    if (NumberUtil.isGreater(loanInfo.getLoanAmount(), maxAmount)) {
                        failureMessage.add(StrUtil.format(
                                "贷款合同号:{}的存量数据导入前检查失败，原因为字段{}超过最大限制:{}(万元)，请检查输入值",
                                loanInfo.getLoanContractNo(), "贷款金额", maxAmount));
                    }
                });
        // 前置检查4：对日期字段进行格式化并记录可能发生的问题
        SimpleDateFormat fallbackSdf = new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy", Locale.ENGLISH);
        loanInfoList.stream()
                .peek(loanInfo -> {
                    loanInfo.setLoanStart(tryToParseDateStr(loanInfo.getLoanStart(), fallbackSdf));
                    if (StrUtil.isBlank(loanInfo.getLoanStart())) {
                        failureMessage.add(StrUtil.format("贷款合同号:{}的存量数据导入前检查失败，原因为解析贷款起始日期字段失败"));
                    }
                })
                .peek(loanInfo -> {
                    loanInfo.setApproveStart(tryToParseDateStr(loanInfo.getApproveStart(), fallbackSdf));
                    if (StrUtil.isBlank(loanInfo.getApproveStart())) {
                        failureMessage.add(StrUtil.format("贷款合同号:{}的存量数据导入前检查失败，原因为解析授信到期日期字段失败"));
                    }
                })
                .forEach(loanInfo -> {
                    loanInfo.setApproveEnd(tryToParseDateStr(loanInfo.getApproveEnd(), fallbackSdf));
                    if (StrUtil.isBlank(loanInfo.getApproveEnd())) {
                        failureMessage.add(StrUtil.format("贷款合同号:{}的存量数据导入前检查失败，原因为解析授信到期日期字段失败"));
                    }
                });

    }

    /**
     * 解析excel日期
     *
     * @param dateStrToParse 待解析的日期字符串
     * @param fallbackSdf    保底的特殊格式化工具
     * @return 解析后的日期字符串
     */
    private String tryToParseDateStr(String dateStrToParse, SimpleDateFormat fallbackSdf) {

        try {
            return DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, DateUtils.dateTime("yyyy-MM-dd", dateStrToParse));
        } catch (Exception e) {
            try {
                return DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, DateUtils.dateTime("yyyy/MM/dd HH:mm:ss", dateStrToParse));
            } catch (Exception e1) {
                try {
                    return DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, DateUtils.dateTime("yyyy/MM/dd HH:mm", dateStrToParse));
                } catch (Exception e2) {
                    try {
                        return DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, fallbackSdf.parse(dateStrToParse));
                    } catch (ParseException ex) {
                        return StrUtil.EMPTY;
                    }
                }
            }
        }

    }

    /**
     * 补全其它数据
     *
     * @param loanInfoList 待导入数据集合
     * @return 补全后的待导入数据集合
     */
    private List<InventoryLoanInfo> fillWithOtherInfo(List<InventoryLoanInfo> loanInfoList) {
        final Date operTime = new Date();
        LoginUser loginUser = SecurityUtils.getLoginUser();
        final String operBy = String.valueOf(loginUser.getUser().getUserId());
        return loanInfoList.stream()
                .peek(loanInfo -> Optional.ofNullable(loanInfo.getLoanContractNo())
                                .map(inventoryLoanInfoMapper::selectInventoryLoanInfoByLoanContractNo)
                        /*.map(InventoryLoanInfo::getId)
                        .ifPresent(loanInfo::setId)*/)
                .peek(loanInfo -> {
                    if (Objects.isNull(loanInfo.getId())) {
                        loanInfo.setCreateBy(operBy);
                        loanInfo.setCreateTime(operTime);
                    } else {
                        loanInfo.setUpdateBy(operBy);
                        loanInfo.setUpdateTime(operTime);
                    }
                })
                .peek(loanInfo -> Optional.ofNullable(loanInfo.getCustomerName())
                        .map(fspCorInfMapper::selectCustomerIdByCustomerName)
                        .ifPresent(loanInfo::setCustomerId))
                .collect(Collectors.toList());
    }

    /**
     * 对每条存量数据对象，检查其字段值是否不为NULL
     * 如果为NULL则返回一条错误消息
     * 否则返回空字符串
     *
     * @param loanInfo 存量数据对象
     * @param field    字段对象
     * @return 错误消息
     */
    private String getFailureMessageIfNullFieldValue(InventoryLoanInfo loanInfo, Field field) {
        Object value = ReflectUtil.getFieldValue(loanInfo, field);
        if (Objects.isNull(value)) {
            return StrUtil.format("贷款合同号:{}的存量数据导入前检查失败，原因为字段{}值为空",
                    loanInfo.getLoanContractNo(), getExcelFieldName(Objects.requireNonNull(field)));
        } else {
            return StrUtil.EMPTY;
        }
    }

    private String getExcelFieldName(@NotNull Field field) {
        if (hasExcelAnnotation(field)) {
            return Stream.of(field.getAnnotationsByType(Excel.class))
                    .findFirst()
                    .map(Excel::name)
                    .orElseThrow(() -> new IllegalArgumentException(StrUtil.format("指定的字段{}中没有指定类型的注解:{}",
                            field.getName(), Excel.class.getCanonicalName())));
        } else {
            throw new IllegalArgumentException(StrUtil.format("指定的字段{}中没有指定类型的注解:{}",
                    field.getName(), Excel.class.getCanonicalName()));
        }
    }


    public void  fillBatchImportLoanDetail(InventoryLoanInfo loanInfo){
        try {
            BatchImportLoanDetail batchImportLoanDetail = new BatchImportLoanDetail(loanInfo.getCustomerName(), loanInfo.getUniscid(),
                    loanInfo.getAreaCode(), loanInfo.getLoanContractNo(), loanInfo.getId().toString());
            batchImportLoanDetail.setCreateBy(loanInfo.getCreateBy());
            batchImportLoanDetailMapper.insertBatchImportLoanDetail(batchImportLoanDetail);
            log.info("同步{}数据成功",loanInfo.getId());
        } catch (Exception e) {
            log.info("同步{}数据出错",loanInfo.getId());
            e.printStackTrace();
        }
    }

}
